package com.hqd.ch03.v26.jdbc.core;

import com.hqd.ch03.v26.jdbc.support.JdbcUtils;
import com.hqd.ch03.v26.utils.DataSourceUtils;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.text.CaseUtils;

import javax.sql.DataSource;
import java.sql.*;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

@Data
@NoArgsConstructor
public class SimpleJdbcTemplate {
    private DataSource dataSource;
    private int fetchSize = -1;
    private int maxRows = -1;

    private int queryTimeout = -1;

    public int upate(String sql, Object... params) throws SQLException {
        return execute(sql, new UpdateStatementCallback(), params);
    }

    public <T> List<T> queryForClass(String sql, Class<T> clazz, Object... params) throws SQLException {
        return (List<T>) execute(sql, new QueryStatementCallback(clazz), params);
    }

    public List<Map<String, Object>> queryForMap(String sql, Object... params) throws SQLException {
        return execute(sql, new QueryMapStatementCallback(), params);
    }

    private <T> T execute(String sql, StatementCallback<T> sc, Object... params) throws SQLException {
        Connection con = DataSourceUtils.getConnection(obtainDataSource());
        PreparedStatement stmt = null;
        try {
            stmt = con.prepareStatement(sql);
            applyStatementSettings(stmt);
            if (ArrayUtils.isNotEmpty(params)) {
                for (int i = 0; i < params.length; i++) {
                    stmt.setObject(i + 1, params[i]);
                }
            }
            return sc.doInStatement(sql, stmt);
        } catch (Exception ex) {
            JdbcUtils.closeStatement(stmt);
            stmt = null;
            DataSourceUtils.releaseConnection(con, getDataSource());
            con = null;
            throw new RuntimeException("execute sql error:" + ex.getMessage());
        } finally {
            JdbcUtils.closeStatement(stmt);
            DataSourceUtils.releaseConnection(con, getDataSource());
        }
    }

    private List<Map<String, Object>> packageResult(ResultSet rs) throws SQLException {
        List<Map<String, Object>> list = new ArrayList<>();
        if (rs != null) {
            ResultSetMetaData metaData = rs.getMetaData();
            int columnCount = metaData.getColumnCount();
            while (rs.next()) {
                Map<String, Object> rowMap = new LinkedHashMap<>();
                // 遍历每一列，将列名和值放入行Map
                for (int i = 1; i <= columnCount; i++) {
                    String columnName = metaData.getColumnLabel(i); // 获取列名
                    Object columnValue = rs.getObject(i);    // 获取列值
                    rowMap.put(columnName, columnValue);
                }
                list.add(rowMap);
            }
        }
        return list;
    }

    private <T> List<T> packageResult(ResultSet rs, Class<T> clazz) throws Exception {
        List<T> list = new ArrayList<>();
        if (rs != null) {
            ResultSetMetaData metaData = rs.getMetaData();
            int columnCount = metaData.getColumnCount();
            while (rs.next()) {
                T t = clazz.getConstructor().newInstance();
                // 遍历每一列，将列名和值放入行Map
                for (int i = 1; i <= columnCount; i++) {
                    String columnName = metaData.getColumnLabel(i); // 获取列名
                    Object value = rs.getObject(i);    // 获取列值
                    String fieldName = CaseUtils.toCamelCase(columnName, false, '_');
                    org.apache.commons.beanutils.BeanUtils.setProperty(t, fieldName, value);
                }
                list.add(t);
            }
        }
        return list;
    }

    protected void applyStatementSettings(Statement stmt) throws SQLException {
        int fetchSize = getFetchSize();
        if (fetchSize != -1) {
            stmt.setFetchSize(fetchSize);
        }
        int maxRows = getMaxRows();
        if (maxRows != -1) {
            stmt.setMaxRows(maxRows);
        }
        DataSourceUtils.applyTimeout(stmt, getDataSource(), getQueryTimeout());
    }

    protected DataSource obtainDataSource() {
        return getDataSource();
    }

    class UpdateStatementCallback implements StatementCallback<Integer> {
        private Class<?> clazz;

        @Override
        public Integer doInStatement(String sql, PreparedStatement stmt) throws SQLException {
            return stmt.executeUpdate();
        }
    }

    @AllArgsConstructor
    class QueryStatementCallback implements StatementCallback<List<?>> {
        private Class<?> clazz;

        @Override
        public List<?> doInStatement(String sql, PreparedStatement stmt) throws SQLException {
            ResultSet rs = null;
            try {
                rs = stmt.executeQuery();
                return packageResult(rs, clazz);
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                JdbcUtils.closeResultSet(rs);
            }
        }
    }

    class QueryMapStatementCallback implements StatementCallback<List<Map<String, Object>>> {
        @Override
        public List<Map<String, Object>> doInStatement(String sql, PreparedStatement stmt) throws SQLException {
            ResultSet rs = null;
            try {
                rs = stmt.executeQuery(sql);
                return packageResult(rs);
            } finally {
                JdbcUtils.closeResultSet(rs);
            }
        }
    }
}
